angular.module('hover-menu', ['ui.bootstrap.position'])

    .constant('hoverDropdownConfig', {
        appendToOpenClass: 'uib-dropdown-open',
        openClass: 'open'
    })

    .service('hoverDropdownService', ['$document', '$rootScope','$timeout', function($document, $rootScope,$timeout) {
        var openScope = null;
        this.timer=null;

        this.open = function(dropdownScope, element) {
            if (!openScope) {
                $document.on('click', closeDropdown);
            }

            if (openScope && openScope !== dropdownScope) {
                openScope.isOpen = false;
            }

            openScope = dropdownScope;
        };

        this.close = function(dropdownScope, element) {
            if (openScope === dropdownScope) {
                openScope = null;
                $document.off('click', closeDropdown);
                $document.off('keydown', this.keybindFilter);
            }
        };

        var closeDropdown = function(evt) {
            // This method may still be called during the same mouse event that
            // unbound this event handler. So check openScope before proceeding.
            if (!openScope) { return; }

            if (evt && openScope.getAutoClose() === 'disabled') { return; }

            if (evt && evt.which === 3) { return; }

            var toggleElement = openScope.getToggleElement();
            if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
                return;
            }

            var dropdownElement = openScope.getDropdownElement();
            if (evt && openScope.getAutoClose() === 'outsideClick' &&
                dropdownElement && dropdownElement[0].contains(evt.target)) {
                return;
            }

            openScope.isOpen = false;
            openScope.focusToggleElement();

            if (!$rootScope.$$phase) {
                openScope.$apply();
            }
        };

        this.keybindFilter = function(evt) {
            if(openScope){
                var dropdownElement = openScope.getDropdownElement();
                var toggleElement = openScope.getToggleElement();
                var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);
                var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);
                if (evt.which === 27) {
                    evt.stopPropagation();
                    openScope.focusToggleElement();
                    closeDropdown();
                } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
                    evt.preventDefault();
                    evt.stopPropagation();
                    openScope.focusDropdownEntry(evt.which);
                }
            }
        };
    }])

    .controller('hoverDropdownController', ['$scope', '$element', '$attrs', '$parse', 'hoverDropdownConfig', 'hoverDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', '$timeout', function($scope, $element, $attrs, $parse, hoverDropdownConfig, hoverDropdownService, $animate, $position, $document, $compile, $templateRequest, $timeout) {
        var self = this,
            scope = $scope.$new(), // create a child scope so we are not polluting original one
            templateScope,
            appendToOpenClass = hoverDropdownConfig.appendToOpenClass,
            openClass = hoverDropdownConfig.openClass,
            getIsOpen,
            setIsOpen = angular.noop,
            toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
            appendToBody = false,
            appendTo = null,
            keynavEnabled = false,
            selectedOption = null,
            timer = null,
            body = $document.find('body');

        $element.addClass('dropdown');

        this.init = function() {
            if ($attrs.isOpen) {
                getIsOpen = $parse($attrs.isOpen);
                setIsOpen = getIsOpen.assign;

                $scope.$watch(getIsOpen, function(value) {
                    scope.isOpen = !!value;
                });
            }

            if (angular.isDefined($attrs.dropdownAppendTo)) {
                var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
                if (appendToEl) {
                    appendTo = angular.element(appendToEl);
                }
            }

            appendToBody = angular.isDefined($attrs.hoverMenuAppendToBody);
            keynavEnabled = angular.isDefined($attrs.keyboardNav);

            if (appendToBody && !appendTo) {
                appendTo = body;
            }

            if (appendTo && self.dropdownMenu) {
                appendTo.append(self.dropdownMenu);
                $element.on('$destroy', function handleDestroyEvent() {
                    self.dropdownMenu.remove();
                });
            }
        };

        this.toggle = function(open) {
            // scope.isOpen = arguments.length ? !!open : !scope.isOpen;
            scope.isOpen = true;
            if (angular.isFunction(setIsOpen)) {
                setIsOpen(scope, scope.isOpen);
            }

            return scope.isOpen;
        };

        this.showDropDownMenu = function () {
            scope.isOpen = true;
        }

        this.closeDropDownMenu = function () {
            scope.isOpen = false;
        }

        // Allow other directives to watch status
        this.isOpen = function() {
            return scope.isOpen;
        };

        scope.getToggleElement = function() {
            return self.toggleElement;
        };

        scope.getAutoClose = function() {
            return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
        };

        scope.getElement = function() {
            return $element;
        };

        scope.isKeynavEnabled = function() {
            return keynavEnabled;
        };

        scope.focusDropdownEntry = function(keyCode) {
            var elems = self.dropdownMenu ? //If append to body is used.
                angular.element(self.dropdownMenu).find('a') :
                $element.find('ul').eq(0).find('a');

            switch (keyCode) {
                case 40: {
                    if (!angular.isNumber(self.selectedOption)) {
                        self.selectedOption = 0;
                    } else {
                        self.selectedOption = self.selectedOption === elems.length - 1 ?
                            self.selectedOption :
                            self.selectedOption + 1;
                    }
                    break;
                }
                case 38: {
                    if (!angular.isNumber(self.selectedOption)) {
                        self.selectedOption = elems.length - 1;
                    } else {
                        self.selectedOption = self.selectedOption === 0 ?
                            0 : self.selectedOption - 1;
                    }
                    break;
                }
            }
            elems[self.selectedOption].focus();
        };

        scope.getDropdownElement = function() {
            return self.dropdownMenu;
        };

        scope.focusToggleElement = function() {
            if (self.toggleElement) {
                self.toggleElement[0].focus();
            }
        };

        scope.$watch('isOpen', function(isOpen, wasOpen) {
            if (appendTo && self.dropdownMenu) {
                var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
                    css,
                    rightalign,
                    scrollbarPadding,
                    scrollbarWidth = 0;

                css = {
                    top: pos.top - 32 + 'px',
                    display: isOpen ? 'block' : 'none'
                };

                // console.log('=======self.dropdownMenu')
                // console.log(self.dropdownMenu)
                rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
                var toggleElementWidth = self.toggleElement.offsetWidth || 160;
                if (!rightalign) {
                    css.left = pos.left + toggleElementWidth + 'px';
                    css.right = 'auto';
                } else {
                    css.left = 'auto';
                    scrollbarPadding = $position.scrollbarPadding(appendTo);

                    if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
                        scrollbarWidth = scrollbarPadding.scrollbarWidth;
                    }

                    css.right = window.innerWidth - scrollbarWidth -
                        (pos.left + $element.prop('offsetWidth')) + 'px';
                }

                // Need to adjust our positioning to be relative to the appendTo container
                // if it's not the body element
                if (!appendToBody) {
                    var appendOffset = $position.offset(appendTo);

                    css.top = pos.top - appendOffset.top + 'px';

                    if (!rightalign) {
                        css.left = pos.left - appendOffset.left + 'px';
                    } else {
                        css.right = window.innerWidth -
                            (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
                    }
                }

                self.dropdownMenu.css(css);
            }

            var openContainer = appendTo ? appendTo : $element;
            var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);

            if (hasOpenClass === !isOpen) {
                $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
                    if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
                        toggleInvoker($scope, { open: !!isOpen });
                    }
                });
            }

            if (isOpen) {
                if (self.dropdownMenuTemplateUrl) {
                    $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
                        templateScope = scope.$new();
                        $compile(tplContent.trim())(templateScope, function(dropdownElement) {
                            var newEl = dropdownElement;
                            self.dropdownMenu.replaceWith(newEl);
                            self.dropdownMenu = newEl;
                            $document.on('keydown', hoverDropdownService.keybindFilter);
                        });
                    });
                } else {
                    $document.on('keydown', hoverDropdownService.keybindFilter);
                }

                scope.focusToggleElement();
                hoverDropdownService.open(scope, $element);
            } else {
                hoverDropdownService.close(scope, $element);
                if (self.dropdownMenuTemplateUrl) {
                    if (templateScope) {
                        templateScope.$destroy();
                    }
                    var newEl = angular.element('<ul class="dropdown-menu"></ul>');
                    self.dropdownMenu.replaceWith(newEl);
                    self.dropdownMenu = newEl;
                }

                self.selectedOption = null;
            }

            if (angular.isFunction(setIsOpen)) {
                setIsOpen($scope, isOpen);
            }
        });
    }])

    .directive('hoverDropdown', function() {
        return {
            controller: 'hoverDropdownController',
            link: function(scope, element, attrs, dropdownCtrl) {
                dropdownCtrl.init();
            }
        };
    })

    .directive('hoverDropdownMenu', function($timeout, hoverDropdownService) {
        return {
            restrict: 'A',
            require: '?^hoverDropdown',
            link: function(scope, element, attrs, dropdownCtrl) {
                if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
                    return;
                }

                element.addClass('hover-dropdown-menu');

                var tplUrl = attrs.templateUrl;
                if (tplUrl) {
                    dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
                }

                var cancelTimer = function(){
                    if(hoverDropdownService.timer){
                        $timeout.cancel(hoverDropdownService.timer)
                    }
                }
                element.bind('mouseenter', cancelTimer);
                element.bind('mouseleave', function(){
                    scope.$apply(function() {
                        dropdownCtrl.closeDropDownMenu();
                    });
                });
                if (!dropdownCtrl.dropdownMenu) {
                    dropdownCtrl.dropdownMenu = element;
                }
            }
        };
    })

    .directive('hoverDropdownToggle', function($timeout,hoverDropdownService) {
        return {
            require: '?^hoverDropdown',
            link: function(scope, element, attrs, dropdownCtrl) {
                if (!dropdownCtrl) {
                    return;
                }

                element.addClass('hover-dropdown-toggle');

                dropdownCtrl.toggleElement = element;

                var showDropdown = function(event) {

                    event && event.preventDefault();

                    if (!element.hasClass('disabled') && !attrs.disabled) {
                        scope.$apply(function() {
                            dropdownCtrl.showDropDownMenu();
                        });
                    }
                };
                var closeDropdown = function (e) {
                    e && e.preventDefault();

                    if (!element.hasClass('disabled') && !attrs.disabled) {
                        scope.$apply(function() {
                            dropdownCtrl.closeDropDownMenu();
                        });
                    }
                }

                element.bind('mouseenter', showDropdown);
                element.bind('mouseleave', function(e){
                    hoverDropdownService.timer = $timeout(function(e){
                        closeDropdown(e)
                    },100);
                });

                // WAI-ARIA
                element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
                scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
                    element.attr('aria-expanded', !!isOpen);
                });

                scope.$on('$destroy', function() {
                    element.unbind('mouseenter', showDropdown);
                });
            }
        };
    });